package com.ruoyi.framework.security.dingtalk;

import cn.hutool.extra.servlet.ServletUtil;
import com.ruoyi.dingtalk.common.CommonValue;
import com.ruoyi.dingtalk.service.DdUserService;
import com.ruoyi.dingtalk.service.DingTalkApiService;
import com.ruoyi.dingtalk.util.DingTalkUtil;
import com.ruoyi.system.service.ISysConfigService;
import com.taobao.api.ApiException;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Setter
@Slf4j
public class DingTalkLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private DingTalkApiService dingTalkApiService;

    private ISysConfigService iSysConfigService;

    private DdUserService ddUserService;

    private UserDetailsService userDetailsService;

    public DingTalkLoginAuthenticationFilter(RequestMatcher requestMatcher) {
        super(requestMatcher);
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        Map<String, String> paramMap = ServletUtil.getParamMap(request);
        String code = paramMap.get("code");
        if (StringUtils.isBlank(code)) {
            throw new BadCredentialsException("钉钉免登失败,CODE不能为空!");
        }
        DingTalkAuthenticationToken unauthenticatedToken = DingTalkAuthenticationToken.unauthenticated(null, code);
        setDetails(request, unauthenticatedToken);
        return authenticate(unauthenticatedToken);
    }

    private DingTalkAuthenticationToken authenticate(DingTalkAuthenticationToken unauthenticatedToken) {
        String dingCode = (String) unauthenticatedToken.getCredentials();
        String appKey = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_KEY_CONFIG_KEY);
        String appSecret = iSysConfigService.selectConfigByKey(CommonValue.DD_APP_SECRET_CONFIG_KEY);
        try {
            String accessToken = dingTalkApiService.getAccessToken(appKey, appSecret);
            String dingUserIdByAuthCode = DingTalkUtil.getUserIdByAuthCode(dingCode, accessToken);
            String userNameByDingUserId = ddUserService.getUserNameByDingUserId(dingUserIdByAuthCode);
            if (StringUtils.isBlank(userNameByDingUserId)) {
                throw new UsernameNotFoundException("没有找到关于钉钉UserId:" + dingUserIdByAuthCode + "相关的用户!");
            }
            UserDetails userDetails = userDetailsService.loadUserByUsername(userNameByDingUserId);
            boolean accountNonLocked = userDetails.isAccountNonLocked();
            if (!accountNonLocked) {
                throw new LockedException("用户帐户已锁定");
            }
            boolean enabled = userDetails.isEnabled();
            if (!enabled) {
                throw new DisabledException("用户已禁用!");
            }
            boolean accountNonExpired = userDetails.isAccountNonExpired();
            if (!accountNonExpired) {
                throw new AccountExpiredException("用户帐户已过期");
            }
            if (!userDetails.isCredentialsNonExpired()) {
                throw new CredentialsExpiredException("用户凭证已过期!");
            }
            return createSuccessAuthentication(unauthenticatedToken, userDetails);
        } catch (ApiException e) {
            log.error("钉钉认证登录Api异常:", e);
            throw new BadCredentialsException(e.getMessage());
        }
    }

    private DingTalkAuthenticationToken createSuccessAuthentication(DingTalkAuthenticationToken unauthenticatedToken, UserDetails userDetails) {
        DingTalkAuthenticationToken authenticated = DingTalkAuthenticationToken.authenticated(userDetails, StringUtils.EMPTY, userDetails.getAuthorities());
        authenticated.setDetails(unauthenticatedToken.getDetails());
        return authenticated;
    }

    /**
     * 提供，以便子类可以配置放入身份验证请求的详细信息属性的内容。
     *
     * @param request                     请求
     * @param dingTalkAuthenticationToken 凭证
     */

    private void setDetails(HttpServletRequest request, DingTalkAuthenticationToken dingTalkAuthenticationToken) {
        dingTalkAuthenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

}
